﻿using System;
using dnAnalytics.LinearAlgebra;
using dnAnalytics.LinearAlgebra.Decomposition;
using NUnit.Framework;

namespace dnAnalytics.Tests.LinearAlgebra.Decomposition
{
    public abstract class CholeskyTests
    {
        private Cholesky mCholesky;
        private Matrix mMatrix;
        private Matrix mNotPDMatrix;
        private Cholesky mNotPostiveDefinite;

        protected abstract Matrix GetMatrix(string file);
        protected abstract Matrix GetMatrix(int size);
        protected abstract Matrix GetMatrix(int rows, int columns);
        protected abstract Matrix GetIdentityMatrix(int size);
        protected abstract Vector GetVector(int size);

        [TestFixtureSetUp]
        public void SetUp()
        {
            mMatrix = GetMatrix("./TestData/Matrices/hilbert_real_symmetric_array_10.mtx");
            mCholesky = new Cholesky(mMatrix);
            mNotPDMatrix = GetMatrix("./TestData/Matrices/gear_integer_general_coordinate_100.mtx");
            mNotPostiveDefinite = new Cholesky(mNotPDMatrix);
        }

        [Test]
        [ExpectedException(typeof (ArgumentNullException))]
        public void CholeskyConstructorNull()
        {
            var cholesky = new Cholesky(null);
        }

        [Test]
        [ExpectedException(typeof (MatrixNotSquareException))]
        public void CholeskyConstructor()
        {
            var matrix = GetMatrix(3, 2);
            var cholesky = new Cholesky(matrix);
        }

        [Test]
        public void PositiveDefinite()
        {
            Assert.IsTrue(mCholesky.IsPositiveDefinite());
        }

        [Test]
        public void NotPositiveDefinite()
        {
            Assert.IsFalse(mNotPostiveDefinite.IsPositiveDefinite());
        }

        [Test]
        public void Factor()
        {
            var factor = mCholesky.Factor();
            var result = factor*factor.Transpose();

            for (var i = 0; i < result.Rows; i++)
            {
                for (var j = 0; j < result.Columns; j++)
                {
                    double error;
                    if (mMatrix[i, j] != 0)
                    {
                        error = System.Math.Abs((result[i, j] - mMatrix[i, j])/mMatrix[i, j]);
                    }
                    else
                    {
                        error = System.Math.Abs(result[i, j] - mMatrix[i, j]);
                    }

                    Assert.IsTrue(error < Constants.AcceptableError);
                }
            }
        }

        [Test]
        public void Determinant()
        {
            var expected = 2.183E-53;
            var actual = mCholesky.Determinant();
            Assert.AreEqual(expected, actual, Constants.IOAcceptableError);
        }

        [Test]
        public void Determinant_NotPositiveDefinite_ThrowsNotPositiveDefiniteException()
        {
            Assert.Throws<NotPositiveDefiniteException>(() => mNotPostiveDefinite.Determinant());
        }

        [Test]
        public void Solve()
        {
            var identity = GetIdentityMatrix(mMatrix.Rows);
            var result = mCholesky.Solve(identity);
            result = mMatrix*result;

            for (var i = 0; i < result.Rows; i++)
            {
                for (var j = 0; j < result.Columns; j++)
                {
                    if (i == j)
                    {
                        Assert.AreEqual(1, result[i, j], Constants.AcceptableCholeskySolveError);
                    }
                    else
                    {
                        Assert.AreEqual(0, result[i, j], Constants.AcceptableCholeskySolveError);
                    }
                }
            }
        }

        [Test]
        public void Solve_NotPositiveDefinite_ThrowsNotPositiveDefiniteException()
        {
            var identity = GetIdentityMatrix(mNotPDMatrix.Rows);
            Assert.Throws<NotPositiveDefiniteException>(() => mNotPostiveDefinite.Solve(identity));
        }

        [Test]
        public void Factor_NotPositiveDefinite_ThrowsNotPositiveDefiniteException()
        {
            Assert.Throws<NotPositiveDefiniteException>(() => mNotPostiveDefinite.Factor());
        }

        [Test]
        public void Solve_ThrowsNotConformable()
        {
            Matrix matrix = GetMatrix(5);
            Assert.Throws<NotConformableException>(() => mCholesky.Solve(matrix));
        }

        [Test]
        public void Solve_Result_ThrowsNotConformable()
        {
            Matrix result = GetMatrix(5, mMatrix.Columns);
            Assert.Throws<NotConformableException>(() => mCholesky.Solve(mMatrix, result));
            result = GetMatrix(mMatrix.Rows, 5);
            Assert.Throws<NotConformableException>(() => mCholesky.Solve(mMatrix, result));
        }

        [Test]
        public void Solve_NullMatrix_ThrowsArgumentNullException()
        {
            Matrix result = GetMatrix(mMatrix.Rows, mMatrix.Columns);
            Assert.Throws<ArgumentNullException>(() => mCholesky.Solve(null, result));
        }

        [Test]
        public void Solve_NullResult_ThrowsArgumentNullException()
        {
            Matrix matrix = GetMatrix(mMatrix.Rows, mMatrix.Columns);
            Assert.Throws<ArgumentNullException>(() => mCholesky.Solve(matrix, null));
        }

        [Test]
        public void SolveVector_NotPositiveDefinite_ThrowsNotPositiveDefiniteException()
        {
            Vector vector = GetVector(mNotPDMatrix.Rows);
            Assert.Throws<NotPositiveDefiniteException>(() => mNotPostiveDefinite.Solve(vector));
        }

        [Test]
        public void SolveVector_NullVector_ThrowsArgumentNullException()
        {
            Vector result = GetVector(mMatrix.Rows);
            Assert.Throws<ArgumentNullException>(() => mCholesky.Solve(null, result));
        }

        [Test]
        public void SolveVector_NullResult_ThrowsArgumentNullException()
        {
            Vector vector = GetVector(mMatrix.Rows);
            Assert.Throws<ArgumentNullException>(() => mCholesky.Solve(vector, null));
        }

        [Test]
        public void SolveVector_ThrowsNotConformable()
        {
            Vector vector = GetVector(5);
            Assert.Throws<NotConformableException>(() => mCholesky.Solve(vector));
        }

        [Test]
        public void SolveVector_Result_ThrowsNotConformable()
        {
            Vector vector = GetVector(mMatrix.Rows);
            Vector result = GetVector(5);
            Assert.Throws<NotConformableException>(() => mCholesky.Solve(vector, result));

        }

    }
}